package com.flute.framework.search;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.TopDocs;

import com.flute.framework.configuration.SearchInformation;
import com.flute.framework.exception.SearchException;
import com.flute.framework.search.result.MarkedResultProperty;
import com.flute.framework.search.result.ResultInfo;
import com.flute.framework.search.sort.IRecordScorer;
import com.flute.tools.util.collection.BottomList;
import com.flute.tools.util.collection.IWeightable;
import com.flute.tools.util.collection.TopList;

public class BlockResultUtil {

	public static List<ResultInfo> getResultInfos(IRecordScorer scorer,
			SearchRequest request, TopDocs tds, Searcher searcher,
			SearchInformation info) throws SearchException {
		List<ResultInfo> resultList = new ArrayList<ResultInfo>();

		if (tds.totalHits > 0) {
			// 计算结果的开始与结束
			int start = request.getNumberPerPage()
					* (request.getPageNumber() - 1);
			int end = start + request.getNumberPerPage();

			ScoreDoc[] sds = tds.scoreDocs;

			// 计算大的结果范围的开始与结束
			int blockStart = getRangeStart(request.getPageNumber(), request
					.getNumberPerPage(), tds);
			int blockEnd = getRangeEnd(request.getPageNumber(), request
					.getNumberPerPage(), tds);

			// 计算分块结果的块开始
			List<Integer> newBlockBegins = new ArrayList<Integer>();
			float lastScore = sds[blockStart].score;
			newBlockBegins.add(blockStart);
			for (int i = blockStart; i <= blockEnd; i++) {
				if (lastScore == sds[i].score) {
					continue;
				} else {
					newBlockBegins.add(i);
					lastScore = sds[i].score;
				}
			}

			// 如果只有一个结果块
			for (int i = 0; i < newBlockBegins.size(); i++) {
				int bs = 0;
				int be = 0;
				if (i == newBlockBegins.size() - 1) {
					bs = newBlockBegins.get(i);
					be = blockEnd;
				} else {
					bs = newBlockBegins.get(i);
					be = newBlockBegins.get(i + 1) - 1;
				}
				if (be <= end && bs >= start) {
					// start...bs...be...end
					if (bs- start >= end - be) {
						// start....................bs...be...end
						addBottom(bs, be, start, end, scorer, request, info,
								searcher, sds, resultList);
					} else {
						// start...bs...be.....................end
						addTop(bs, be, start, end, scorer, request, info,
								searcher, sds, resultList);
					}
				} else if (be <= end && bs < start) {
					// (b1S....start...b1E)__________b2S...end...
					addBottom(bs, be, start, end, scorer, request, info,
							searcher, sds, resultList);
				} else if (be > end && bs >= start) {
					// ....start...b1E__________(b2S...end...b2E)
					addTop(bs, be, start, end, scorer, request, info, searcher,
							sds, resultList);
				} else {
					// ...start...b1E________(b2S...b2E)_______b3S...end
					addTop(bs, be, start, end, scorer, request, info, searcher,
							sds, resultList);
				}
			}
		}
		
		return resultList;
	}

	private static void addBottom(int blockStart, int blockEnd, int start,
			int end, IRecordScorer scorer, SearchRequest request,
			SearchInformation info, Searcher searcher, ScoreDoc[] sds,
			List<ResultInfo> resultList) throws SearchException {
		BottomList<ResultInfoWeight> infoList = new BottomList<ResultInfoWeight>(
				blockEnd - start);
		for (int i = blockStart; i <= blockEnd; i++) {
			infoList.add(new ResultInfoWeight(scorer, request, getResultInfo(
					info, searcher, i, sds)));
		}
		for (int i = infoList.size() - 1; i >= Math.max(0, infoList.size()
				- Math.min(blockEnd, end) + start); i--) {
			resultList.add(infoList.getList().get(i).getInfo());
		}
	}

	private static void addTop(int blockStart, int blockEnd, int start,
			int end, IRecordScorer scorer, SearchRequest request,
			SearchInformation info, Searcher searcher, ScoreDoc[] sds,
			List<ResultInfo> resultList) throws SearchException {
		TopList<ResultInfoWeight> infoList = new TopList<ResultInfoWeight>(end
				- blockStart);
		for (int i = blockStart; i <= blockEnd; i++) {
			infoList.add(new ResultInfoWeight(scorer, request, getResultInfo(
					info, searcher, i, sds)));
		}
		for (int i = Math.max(0, start - blockStart); i < infoList.size(); i++) {
			resultList.add(infoList.getList().get(i).getInfo());
		}
	}

	private static ResultInfo getResultInfo(SearchInformation info,
			Searcher searcher, int i, ScoreDoc[] sds) throws SearchException {
		ResultInfo resultInfo = new ResultInfo();
		try {
			for (MarkedResultProperty mrp : info.getParser()
					.getMarkedProperties()) {
				if (!mrp.getName().equals(ISearcher.SCORE)
						&& !mrp.getName().equals(ISearcher.DOC_ID)) {
					Document doc = searcher.doc(sds[i].doc);
					if (doc.getField(mrp.getName()) == null) {
						throw new SearchException("在文档中没有域：'" + mrp.getName()
								+ "'", "");
					}
					resultInfo.addProperty(mrp.getName(), doc.getField(
							mrp.getName()).stringValue());
				}
			}
		} catch (CorruptIndexException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		resultInfo.addProperty(ISearcher.SCORE, String.valueOf(sds[i].score));
		resultInfo.addProperty(ISearcher.DOC_ID, String.valueOf(sds[i].doc));

		return resultInfo;
	}

	private static int getRangeStart(int pagenumber, int numberpage, TopDocs td) {
		ScoreDoc[] sds = td.scoreDocs;
		int start = numberpage * (pagenumber - 1);
		if (sds.length > start) {
			ScoreDoc minsd = sds[start];
			for (int i = start; i >= 0; i--) {
				if (minsd.score < sds[i].score) {
					return i + 1;
				} else {
					minsd = sds[i];
				}
			}
		}

		return 0;
	}

	private static int getRangeEnd(int pagenumber, int numberpage, TopDocs td) {
		ScoreDoc[] sds = td.scoreDocs;
		int start = numberpage * (pagenumber - 1);
		int end = Math.min(start + numberpage, sds.length);
		if (sds.length > end) {
			ScoreDoc maxsd = sds[end];
			for (int i = end; i < sds.length; i++) {
				if (maxsd.score > sds[i].score) {
					return i - 1;
				} else {
					maxsd = sds[i];
				}
			}
		}

		return sds.length - 1;
	}

}

class ResultInfoWeight implements IWeightable {
	private IRecordScorer scorer;
	private SearchRequest request;
	private ResultInfo info;

	public ResultInfoWeight(IRecordScorer scorer, SearchRequest request,
			ResultInfo info) {
		super();
		this.scorer = scorer;
		this.request = request;
		this.info = info;
	}

	@Override
	public double getWeight() {
		return scorer.score(info.getPropertyList(), request);
	}

	public ResultInfo getInfo() {
		return info;
	}

	public void setInfo(ResultInfo info) {
		this.info = info;
	}
}
